package com.sunday.common.statemachine.study.e2_Config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

import java.util.EnumSet;
import java.util.concurrent.TimeUnit;

/**
 * 配置Spring状态机，定义订单状态流转及其事件的处理。
 * 此配置类指定了订单状态及其转换规则，同时设置了状态变更时的监听。
 *
 * @author sunday
 * @since 2024/12/2
 */
@Slf4j
@Configuration  // 声明该类为Spring的配置类
@EnableStateMachine  // 启用Spring状态机功能
public class StateMachineConfig
        extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {  // 继承EnumStateMachineConfigurerAdapter以便使用枚举状态和事件

    /**
     * 配置状态机的基本设置，如自动启动和监听器。
     *
     * @param config 状态机的配置器，用于设置状态机的配置
     */
    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config)
            throws Exception {
        config
                .withConfiguration()  // 配置状态机的基本设置
                .autoStartup(true)  // 启动时自动启动状态机
                .listener(listener())  // 注册状态机的监听器，用于监听状态变更
                .machineId("mymachine");
        /* TODO 测试未成功
         * 动作取消机制：
         * 当设置为StateDoActionPolicy.IMMEDIATE_CANCEL时，如果在一个状态转换正在进行的过程中，
         * 另一个新的事件触发了一个新的状态转换，那么当前正在执行的与状态相关的动作（Action）会立即被取消。这是一种比较严格的策略，
         * 它确保状态机能够快速响应新的状态转换请求，而不会因为正在执行的动作而产生延迟或者复杂的并发行为。
         *
         * 应用场景示例：
         * 假设在一个订单状态机中，当订单状态从PAID转换到SHIPPED时，有一个动作是发送通知给仓库准备发货。
         * 如果在这个通知发送的过程中（可能是因为网络延迟或者其他原因导致动作执行时间较长），
         * 又有一个新的事件（比如用户取消订单）触发了从PAID到CANCELLED的状态转换，那么按照IMMEDIATE_CANCEL策略，
         * 发送通知给仓库的动作会立即停止，状态机直接进行到取消订单的状态转换流程。
         */
//                .stateDoActionPolicy(StateDoActionPolicy.IMMEDIATE_CANCEL)
        /* TODO 测试未成功，有待后续探索
         * TIMEOUT_CANCEL策略详细解释
         * 动作超时取消机制：
         * 当一个状态转换触发了相关的动作（Action）执行时，状态机开始计时。
         * 如果这个动作在规定的 10 秒（在这个配置示例中）内没有完成，那么状态机就会取消这个正在执行的动作。
         * 这与之前提到的IMMEDIATE_CANCEL有所不同，IMMEDIATE_CANCEL是在新的状态转换事件触发时立即取消当前动作，
         * 而TIMEOUT_CANCEL是基于动作执行时间是否超时来决定是否取消。
         * 应用场景示例：
         * 假设在一个复杂的系统状态机中，当从一个状态转换到另一个状态时，需要调用一个外部服务来完成一些数据更新操作作为状态转换的动作。
         * 如果这个外部服务因为网络故障或者性能问题而长时间没有响应，超过了 10 秒的超时时间，那么状态机为了避免无限期等待这个动作完成，
         * 会按照TIMEOUT_CANCEL策略取消这个调用外部服务的动作。之后，状态机可以根据业务逻辑决定是重试这个动作、记录错误并进入错误状态，
         * 还是进行其他相关的状态转换。
         * stateDoActionPolicyTimeout方法的作用：
         * 这个方法用于指定动作执行的超时时间。在上面的配置中，stateDoActionPolicyTimeout(10, TimeUnit.SECONDS)明确了超时时间是 10 秒，
         * 时间单位是秒（TimeUnit.SECONDS）。这使得状态机能够精确地控制动作执行的最长允许时间，确保系统的响应性和稳定性。
         * 不同的业务场景可能需要不同的超时时间设置，例如，对于一些对实时性要求极高的系统，可能会设置较短的超时时间（如 1 - 2 秒），
         * 而对于一些允许一定延迟的后台任务相关的状态转换动作，可能会设置较长的超时时间（如几分钟甚至更长）。
         */
//                .stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL)
//                .stateDoActionPolicyTimeout(3, TimeUnit.SECONDS)
        ;

    }

    /**
     * 配置状态机的状态，包括初始状态和所有可用状态。
     * <a href="https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#configuring-regions">...</a>
     *
     * @param states 状态机的状态配置器，用于定义状态及其属性
     */
    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
//        states
//                .withStates()  // 定义状态机的状态
//                .initial(OrderStates.NEW)  // 设置初始状态为NEW（订单新建）
//                .state(OrderStates.NEW)
//                .and()
//                .withStates()
//                    .parent(OrderStates.PAID)  // 定义父状态PAID
//
//                .and()
//                .withStates()
//                    .state(OrderStates.SHIPPED)
//                    .end(OrderStates.COMPLETED)  // 设置结束状态为COMPLETED（订单完成）
//                    .end(OrderStates.CANCELED);  // 设置结束状态为CANCELED（订单取消）

        states
                .withStates()  // 定义状态机的状态
                .initial(OrderStates.NEW, initialAction())  // 设置初始状态为NEW（订单新建）
                .states(EnumSet.allOf(OrderStates.class))  // 包含所有定义的状态，使用EnumSet来支持枚举类型的状态集合
//                .states(Set.of(OrderStates.NEW, OrderStates.PAID, OrderStates.SHIPPED))
                /*
                 * 相当于 AOP中的 前置 后置通知
                 * 在状态转换前执行的动作
                 * 在状态转换后执行的动作
                 */
                .state(OrderStates.NEW, entryAction(), exitAction())
                .state(OrderStates.PAID)
//                .state(OrderStates.SHIPPED)
                // 进入
                .stateEntry(OrderStates.SHIPPED, action(), errorAction())
//                .stateDo(OrderStates.SHIPPED, throwAction(), errorAction())
                // 执行
                .stateDo(OrderStates.SHIPPED, action(), errorAction())
                // 推出
                .stateExit(OrderStates.SHIPPED, action(), errorAction())
                .end(OrderStates.COMPLETED)  // 设置结束状态为COMPLETED（订单完成）
                .end(OrderStates.CANCELED);  // 设置结束状态为CANCELED（订单取消）
    }


    /**
     * 配置状态机的状态转换规则，包括源状态、目标状态和触发事件。
     *
     * @param transitions 状态机的转换配置器，用于定义状态之间的转换
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions)
            throws Exception {
        transitions
                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.NEW)  // 源状态：NEW
                .target(OrderStates.PAID)  // 目标状态：PAID
                .event(OrderEvents.PAY)  // 触发事件：PAY（支付事件）
                .guard(guard()) // 定义状态转换前的 guard，只有当 guard返回true时才进行状态转换
                .guard(guard1(true))
                /*
                 * 相当与AOP中的环绕通知
                 * 多个可以按照顺序执行
                 */
                .action(action()) // 定义状态转换时的动作，可以执行一些业务逻辑
//                .action(timeOutAction())
//                .action(throwAction(), errorAction())
//                .action(Actions.errorCallingAction(throwAction(), errorAction()))
                .and()  // 结束当前转换定义，开始下一个转换规则
                .withExternal()
                .source(OrderStates.PAID)  // 源状态：PAID
                .target(OrderStates.SHIPPED)  // 目标状态：SHIPPED
                .event(OrderEvents.SHIP)  // 触发事件：SHIP（发货事件）
                //                .guardExpression("true") // 定义状态转换前的 guard，只有当 guard返回true时才进行状态转换
                .and()  // 结束当前转换定义，开始下一个转换规则
                .withExternal()
                .source(OrderStates.SHIPPED)  // 源状态：SHIPPED
                .target(OrderStates.COMPLETED)  // 目标状态：COMPLETED
                .event(OrderEvents.COMPLETE)  // 触发事件：COMPLETE（订单完成事件）
                .and()
                // 新增订单取消的状态转换配置，从不同状态都可以触发取消订单事件，转换到已取消状态
                .withExternal()
                .source(OrderStates.NEW) // 源状态可以是新建订单状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelOrderAction()) // 定义取消订单时执行的动作，比如退款、释放资源等相关操作
                .guard(cancelOrderGuard()) // 可以添加条件判断，例如只有满足一定条件（如用户在规定时间内申请取消等）才允许取消订单
                .and()
                .withExternal()
                .source(OrderStates.PAID) // 源状态也可以是已支付状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelOrderAction()) // 同样执行取消订单的相关操作
                .guard(cancelOrderGuard()) // 添加相应的条件判断
                .and()
                .withExternal()
                .source(OrderStates.SHIPPED) // 源状态还可以是已发货状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelOrderAction()) // 执行取消订单操作，比如通知物流拦截、处理退款等
                .guard(cancelOrderGuard()) // 条件判断，例如发货时间较短、符合取消政策等情况下才允许取消
        ;

    }

    /**
     * 定义一个状态机监听器，监听状态变化并进行日志记录。
     *
     * @return 一个状态机监听器实例
     */
    @Bean  // 定义为Spring的Bean，以便注入到状态机中
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<>() {  // 继承StateMachineListenerAdapter以简化实现
            /**
             * 状态机状态发生变化时调用此方法，输出状态变更日志。
             *
             * @param from 当前状态
             * @param to 目标状态
             */
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("State change from {} to {}", from == null ? "无" : from.getId(), to.getId());  // 输出状态变更日志
//                log.info("State change to {}", to.getId());  // 输出状态变更日志
            }

            /**
             * 状态机触发事件时调用此方法，输出事件日志。
             *
             * @param transition 触发的转换
             */
//            @Override
//            public void transition(Transition<OrderStates, OrderEvents> transition) {
//                log.info("触发事件: {}", transition.getTrigger().getEvent());
//            }
        };
    }

    /**
     * 权限放行
     * 配置 Guard
     * 你可以使用 guard 来保护 state transitions。您可以使用该界面 执行方法有权访问 . 以下示例显示了如何执行此操作：
     * <a href="https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#configuring-guards">...</a>
     */
    @Bean
    public Guard<OrderStates, OrderEvents> guard() {
        return new Guard<>() {

            @Override
            public boolean evaluate(StateContext<OrderStates, OrderEvents> context) {
                /*
                 * 需要注意的是，当 guard 返回 false 时，状态转换将被拒绝。如果 guard 返回 true，则状态转换将继续。
                 * 业务上会一直等待放行
                 */
                boolean randomBoolean = Math.random() < 0.5;
                log.info("StateContext : {}", context);
                log.info("Guard : {}", randomBoolean);
                return randomBoolean;
            }
        };
    }

    /**
     * <a href="https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-thingstoremember">...</a>
     * 要记住的事情
     * 当从 配置，记住 Spring Framework 的工作原理是值得的 和豆子。在下一个示例中，我们定义了一个普通配置，其中 状态 和 4 个过渡。所有过渡 由 或 保护。您必须确保将其创建为真正的 bean，因为它带有 ， while is 不是。S1S2guard1guard2guard1@Beanguard2
     * 这意味着该事件将获得条件 为 ，并将获得条件为 ，因为这些条件是 来自对这些函数的普通方法调用。E3guard2TRUEE4guard2FALSE
     * 但是，由于 被定义为 ，它由 Spring 框架。因此，对其方法的额外调用会导致 只有该实例的一个实例。Event 将首先获取 具有条件的代理实例，而 event 将得到相同的 实例 和 条件 （当方法调用是使用 定义时）。这不是 Spring State Machine 特有的行为。相反，它是 Spring Framework 如何与 bean 一起工作。 以下示例显示了这种安排的工作原理：guard1@BeanE1TRUEE2TRUEFALSE
     */
    @Bean
    public Guard<OrderStates, OrderEvents> guard1(final boolean value) {
        return new Guard<>() {

            @Override
            public boolean evaluate(StateContext<OrderStates, OrderEvents> context) {
                log.info("guard1(final boolean value) === {}", value);
               return value;
            }
        };
    }

    /**
     * 流转中的业务穿插
     * 配置操作
     * 您可以定义要使用过渡和状态执行的操作。 操作始终作为 源自触发器。以下示例说明如何定义操作：
     * <a href="https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#statemachine-config-actions">...</a>
     */
    @Bean
    public Action<OrderStates, OrderEvents> action() {
        return new Action<OrderStates, OrderEvents>() {

            @Override
            public void execute(StateContext<OrderStates, OrderEvents> context) {
//                log();
                // do something
                log.info("Action ============= : {}", context);
            }
        };
    }

    /**
     * 在前面的示例中，single 由 bean named 和 associated 其中状态 、 和 。我们需要澄清一下这里发生了什么：ActionactionS1S2S3
     * <p>
     * 我们为初始状态 定义了一个操作。S1
     * <p>
     * 我们为 state 定义了一个 entry 操作，并将 exit 操作留空。S1
     * <p>
     * 我们为 state 定义了一个 exit 操作，并将 entry 操作留空。S2
     * <p>
     * 我们为状态 定义了一个状态操作。S2
     * <p>
     * 我们为 state 定义了 entry 和 exit 动作。S3
     * <p>
     * 请注意，state 与 and 函数一起使用两次。仅当您想要定义 entry 或 exit 时，才需要执行此操作 具有初始状态的 action。
     */

    @Bean
    public Action<OrderStates, OrderEvents> entryAction() {
        return context -> {
//            log();
            // do something
            log.info("Action entryAction : {}", context);
        };
    }

    @Bean
    public Action<OrderStates, OrderEvents> exitAction() {
        return context -> {
//            log();
            // do something
            log.info("Action exitAction : {}", context);
        };
    }

    @Bean
    public Action<OrderStates, OrderEvents> timeOutAction() {

        return context -> {
            log();
            try {
                // 模拟长时间操作
                log.info("TimeOutAction started.");
                TimeUnit.SECONDS.sleep(10);  // 假设这个操作超时了
                log.info("TimeOutAction finished.");
            } catch (InterruptedException e) {
                log.error("Action interrupted", e);
            }
            // do something
            log.info("Action timeOutAction : {}", context);
        };
    }

    @Bean
    public Action<OrderStates, OrderEvents> throwAction() {
        return new Action<OrderStates, OrderEvents>() {

            @Override
            public void execute(StateContext<OrderStates, OrderEvents> context) {
                log.info("throw exception !!!!!!!");
                throw new RuntimeException("MyError");
            }
        };
    }

    @Bean
    public Action<OrderStates, OrderEvents> errorAction() {
        return new Action<OrderStates, OrderEvents>() {

            @Override
            public void execute(StateContext<OrderStates, OrderEvents> context) {
                // RuntimeException("MyError") added to context
                Exception exception = context.getException();
                String message = exception.getMessage();
                log.info("Action errorAction : {}, error : {}", context, message);
            }
        };
    }

    @Bean
    public Action<OrderStates, OrderEvents> initialAction() {
        return new Action<OrderStates, OrderEvents>() {

            @Override
            public void execute(StateContext<OrderStates, OrderEvents> context) {
                // do something initially
                log.info("Action initialAction : {}", context);
            }
        };
    }

    private void log() {
        log.info("Action timeOutAction is running in thread: {}", Thread.currentThread().getName());
    }


    /**
     * 配置取消订单时执行的动作，例如根据订单所处状态进行相应的退款、通知相关方等操作。
     *
     * @return 一个Action实例，代表取消订单时执行的动作
     */
    @Bean
    public Action<OrderStates, OrderEvents> cancelOrderAction() {
        return context -> {
            log.info("Cancel order action: {}", context);
            OrderStates currentState = context.getStateMachine().getState().getId();
            if (currentState == OrderStates.NEW) {
                // 如果是新建订单取消，可能直接删除订单相关临时数据等
                log.info("Canceling NEW order, perform related operations like deleting temp data.");
            } else if (currentState == OrderStates.PAID) {
                // 如果是已支付订单取消，进行退款等操作
                log.info("Canceling PAID order, perform refund and related operations.");
            } else if (currentState == OrderStates.SHIPPED) {
                // 如果是已发货订单取消，通知物流拦截、处理退款等
                log.info("Canceling SHIPPED order, notify logistics to intercept and handle refund.");
            }
            // 其他通用的取消订单操作，如更新订单状态为已取消等
            log.info("Update order status to CANCELED.");
        };
    }

    /**
     * 配置取消订单时的条件判断Guard，根据业务规则决定是否允许取消订单。
     *
     * @return 一个Guard实例，用于判断是否允许取消订单
     */
    @Bean
    public Guard<OrderStates, OrderEvents> cancelOrderGuard() {
        return new Guard<>() {
            @Override
            public boolean evaluate(StateContext<OrderStates, OrderEvents> context) {
                log.info("Evaluating cancel order guard: {}", context);
                OrderStates currentState = context.getStateMachine().getState().getId();
                log.info("currentState: {}", currentState);
                if (currentState == OrderStates.NEW) {
                    // 假设新建订单在一定时间内都可以无条件取消，这里简单返回true，实际可按业务规则调整
                    return true;
                } else if (currentState == OrderStates.PAID) {
                    // 已支付订单取消可能需要满足一些条件，比如未超过某个退款期限等，这里简单返回true示例，可完善
                    return true;
                } else if (currentState == OrderStates.SHIPPED) {
                    // 已发货订单取消可能要求发货时间较短、符合取消政策等，这里简单返回true示例，可细化规则
                    return true;
                }
                return false;
            }
        };
    }

}
